/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.openide.filesystems;
import java.io.*;
import java.lang.ref.*;
import java.util.*;
import org.openide.TopManager;
import org.openide.util.NbBundle;
import org.openide.util.enum.EmptyEnumeration;
import org.openide.util.io.NbMarshalledObject;
/** Implementation of <code>AbstractFileSystem.Attr</code> using a special file
* in each folder for holding attributes.
* It needs to hide
* the file from the rest of system, so it also implements
* <code>AbstractFileSystem.List</code> to exclude the file from the children list
* (it can then serve to filter a plain list implementation).
*
* @author Jaroslav Tulach
*/
public class DefaultAttributes extends Object
implements AbstractFileSystem.Attr, AbstractFileSystem.List {
static final long serialVersionUID=-5801291358293736478L;
/** File name of special file in each folder where attributes are saved. */
public final static String ATTR_NAME = "filesystem"; // NOI18N
/** Extension of special file in each folder where attributes are saved. */
public final static String ATTR_EXT = "attributes"; // NOI18N
/** Name with extension of special file in each folder where attributes are saved. */
public final static String ATTR_NAME_EXT = ATTR_NAME + '.' + ATTR_EXT;
/** description of the fs to work on - info about files */
private AbstractFileSystem.Info info;
/** description of the fs to work on - work with files */
private AbstractFileSystem.Change change;
/** description of the fs to work on - listing of files */
private AbstractFileSystem.List list;
/** Cache of attributes.
* For name of folder gives map of maps of attibutes
* (String, Reference (Table))
*/
private transient Map cache;
/** Constructor.
* @param info file object information to use
* @param change file change hooks to use
* @param list list to filter (can be <code>null</code>, but then this object cannot work as a list)
*/
public DefaultAttributes (
AbstractFileSystem.Info info,
AbstractFileSystem.Change change,
AbstractFileSystem.List list
) {
this.info = info;
this.change = change;
this.list = list;
}
/** Get the children list, filtering out the special attributes file.
* You <em>must</em> have provided a non-<code>null</code> {@link AbstractFileSystem.List}
* in the constructor for this to work. If you did not, the rest of the class will work
* fine, but this method should not be called and this object should not be used
* as a <code>List</code> implementation.
*
* @param f the folder, by name; e.g. <code>top/next/afterthat</code>
* @return a list of children of the folder, as <code>file.ext</code> (no path)
*/
public String[] children (String f) {
String[] arr = list.children (f);
if (arr == null) {
return null;
}
int size = arr.length;
for (int i = 0; i < size; i++) {
if (ATTR_NAME_EXT.equals (arr[i])) {
// exclude this index
arr[i] = null;
// there can be only one file with attributes
break;
}
}
return arr;
}
// JST: Description
//
//
// The class should be written in such a way that the access to disk is
// synchronized (this). But during the access nobody is allowed to
// perform serialization and deserialization
// of unknown objects, so all objects should be wrapped into NbMarshalledObject
// serialized or in reverse target NbMarshalledObject should be deserialized
// and then not holding the lock the object obtained from it by a call to
// marshall.get ().
//
// JST: Got it?
/* Get the file attribute with the specified name.
* @param name the file
* @param attrName name of the attribute
* @return appropriate (serializable) value or <CODE>null</CODE> if the attribute is unset (or could not be properly restored for some reason)
*/
public Object readAttribute(String name, String attrName) {
Table t;
String[] arr = new String[2];
split (name, arr);
synchronized (this) {
// synchronized so only one table for each folder
// can exist
t = loadTable (arr[0]);
}
// JST:
// had to split the code to do getAttr out of synchronized block
// because the attribute can be serialized FileObject and
// so the code returns back to FileSystem (that is usually synchronized)
//
// this leads to deadlocks between FS & DefaultAttributes implementation
//
// I do not know if the table should not be somehow synchronized,
// but it seems ok.
return t.getAttr (arr[1], attrName);
}
/* Set the file attribute with the specified name.
* @param name the file
* @param attrName name of the attribute
* @param value new value or <code>null</code> to clear the attribute. Must be serializable, although particular file systems may or may not use serialization to store attribute values.
* @exception IOException if the attribute cannot be set. If serialization is used to store it, this may in fact be a subclass such as {@link NotSerializableException}.
*/
public void writeAttribute(String name, String attrName, Object value)
throws IOException {
// create object that should be serialized
NbMarshalledObject marshall = new NbMarshalledObject (value);
String[] arr = new String[2];
split (name, arr);
for (;;) {
int version;
Table t;
synchronized (this) {
t = loadTable (arr[0]);
version = t.version;
}
// Tests if the attribute is changing
Object prev = t.getAttr(arr[1], attrName);
if (prev == value || (value != null && value.equals (prev))) {
return;
}
synchronized (this) {
Table t2 = loadTable (arr[0]);
if (t == t2 && version == t2.version) {
// no modification between reading of the value =>
// save!
t.setMarshalledAttr (arr[1], attrName, marshall);
saveTable (arr[0], t);
// ok, saved
return;
}
}
// otherwise try it again
}
}
/* Get all file attribute names for the file.
* @param name the file
* @return enumeration of keys (as strings)
*/
public synchronized Enumeration attributes(String name) {
String[] arr = new String[2];
split (name, arr);
Table t = loadTable (arr[0]);
return t.attrs (arr[1]);
}
/* Called when a file is renamed, to appropriatelly update its attributes.
* <p>
* @param oldName old name of the file
* @param newName new name of the file
*/
public synchronized void renameAttributes (String oldName, String newName) {
try {
String[] arr = new String[2];
split (oldName, arr);
Table t = loadTable (arr[0]);
Map v = (Map) t.remove (arr[1]);
// System.out.println ("ARg[0] = " + arr[0] + " arr[1] = " + arr[1] + " value: " + v); // NOI18N
if (v == null) {
// no attrs no change
return;
}
split (newName, arr);
// Remove transient attributes:
Iterator it = v.entrySet ().iterator ();
while (it.hasNext ()) {
Map.Entry pair = (Map.Entry) it.next ();
if (FileUtil.transientAttributes.contains (pair.getKey ()))
it.remove ();
}
t.put (arr[1], v);
// System.out.println ("xyz[0] = " + arr[0] + " xyz[1] = " + arr[1] + " value: " + v); // NOI18N
saveTable (arr[0], t);
} catch (IOException e) {
TopManager.getDefault ().notifyException (e);
}
}
/* Called when a file is deleted to also delete its attributes.
*
* @param name name of the file
*/
public synchronized void deleteAttributes (String name) {
try {
String[] arr = new String[2];
split (name, arr);
Table t = loadTable (arr[0]);
if (t.remove (arr[1]) != null) {
// if there is a change
saveTable (arr[0], t);
}
} catch (IOException e) {
TopManager.getDefault ().notifyException (e);
}
}
/** Getter for the cache.
*/
private Map getCache () {
if (cache == null) {
cache = new HashMap (31);
}
return cache;
}
/** Splits name of a file to name of folder and to name of the file.
* @param name of file
* @param arr arr[0] will hold name of folder and arr[1] name of the file
*/
private static void split (String name, String[] arr) {
int i = name.lastIndexOf ('/');
if (i == -1) {
arr[0] = ""; // NOI18N
arr[1] = name;
return;
}
// folder name
arr[0] = name.substring (0, i);
// increase the i to be beyond the length
if (++i == name.length ()) {
arr[1] = ""; // NOI18N
} else {
// split it
arr[1] = name.substring (i);
}
}
/** Save attributes.
* @param name name of folder to save attributes for
* @param map map to save
*/
private void saveTable(String name, Table map) throws IOException {
String fullName = (name.length()==0 ? "": (name + '/')) + ATTR_NAME_EXT; // NOI18N
if (info.folder (fullName)) {
if (map.size () == 0) {
// ok no need to delete
return;
}
// find parent
change.createData (fullName);
// System.out.println ("Done : " + fo); // NOI18N
} else {
if (map.size() == 0) {
change.delete (fullName);
return;
}
}
BufferedOutputStream fos = new BufferedOutputStream(info.outputStream (fullName));
try {
ObjectOutputStream oos = new ObjectOutputStream (fos);
oos.writeObject (map);
oos.flush ();
// } catch (IOException e) {
// throw new IOException (FileSystem.getString("EXC_Cannot_modify", fullName));
} finally {
fos.close ();
}
}
/** Load attributes from cache or
* from disk.
* @param name of folder to load data from
*/
private Table loadTable(String name) { //throws IOException {
Reference r = (Reference)getCache ().get (name);
if (r != null) {
Table m = (Table)r.get ();
if (m != null) {
return m;
}
}
// have to load new table
Table t = load (name);
t.attach (name, this);
getCache ().put (name, new SoftReference (t));
return t;
}
/** Loads the table. Does no initialization.
*/
private Table load (String name) {
String fullName = (name.length()==0 ? "": (name + '/')) + ATTR_NAME_EXT; // NOI18N
if (info.folder (fullName)){
return new Table ();
}
InputStream fis = null;
try {
fis = info.inputStream (fullName);
return loadTable (fis);
} catch (FileNotFoundException ex) {
return new Table ();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
}
}
}
}
/** Remove from cache */
synchronized void removeTable (String name) {
getCache ().remove (name);
}
//
// FileUtil.extractJar methods
//
/** Does the name seems like file with extended attributes?
* @param name the name
* @return true if so
*/
static boolean acceptName (String name) {
return name.endsWith (ATTR_NAME_EXT);
}
/** Loads the Table of extended attributes for a input stream.
* @param is input stream
* @return the attributes table for this input stream
*/
static Table loadTable (InputStream is) {
try {
BufferedInputStream fis = new BufferedInputStream(is);
ObjectInputStream ois = new org.openide.util.io.NbObjectInputStream (fis);
Object o = ois.readObject();
if (o instanceof Table) {
return (Table)o;
}
} catch (IOException e) {
if (System.getProperty ("netbeans.debug.exceptions") != null) e.printStackTrace();
} catch (ClassNotFoundException e) {
if (System.getProperty ("netbeans.debug.exceptions") != null) e.printStackTrace();
}
// create empty table, what else
return new Table ();
}
/** Table that hold mapping between files and attributes.
* Hold mapping of type (String, Map (String, Object))
*/
final static class Table extends HashMap implements Externalizable {
static final long serialVersionUID = 2353458763249746934L;
/** name of folder we belong to */
private transient String name;
/** attributes to belong to */
private transient DefaultAttributes attrs;
/** version counting */
private transient int version = 0;
/** Constructor */
public Table () {
super (11);
}
/** Attaches to file in attributes */
public void attach (String name, DefaultAttributes attrs) {
this.name = name;
this.attrs = attrs;
}
/** Remove itself from the cache if finalized.
*/
protected void finalize () {
// System.out.println ("Finalizing table for: " + name); // NOI18N
attrs.removeTable (name);
}
/** For given file finds requested attribute.
* @param fileName name of the file
* @param attrName name of the attribute
*/
public Object getAttr (String fileName, String attrName) {
Map m = (Map)get (fileName);
if (m != null) {
NbMarshalledObject mo = (NbMarshalledObject)m.get (attrName);
try {
return mo == null ? null : mo.get ();
} catch (IOException ex) {
} catch (ClassNotFoundException ex) {
}
}
return null;
}
/** Sets an marshaled attribute to the table.
*/
final void setMarshalledAttr (
String fileName, String attrName, NbMarshalledObject obj
) {
Map m = (Map)get (fileName);
if (m == null) {
m = new HashMap (7);
put (fileName, m);
}
m.put (attrName, obj);
// increments the version
version++;
}
/** Enum of attributes for one file.
*/
public Enumeration attrs (String fileName) {
Map m = (Map)get (fileName);
if (m == null) {
return EmptyEnumeration.EMPTY;
} else {
HashSet s = new HashSet (m.keySet ());
return Collections.enumeration (s);
}
}
/** Writes external.
*/
public void writeExternal (ObjectOutput oo) throws IOException {
// list of names
Iterator it = keySet ().iterator ();
while (it.hasNext ()) {
String file = (String)it.next ();
Map attr = (Map)get (file);
if (attr != null && !attr.isEmpty ()) {
oo.writeObject (file);
Iterator entries = attr.entrySet ().iterator ();
while (entries.hasNext ()) {
Map.Entry entry = (Map.Entry)entries.next ();
String key = (String)entry.getKey ();
Object value = entry.getValue ();
if (key != null && value != null) {
oo.writeObject (key);
oo.writeObject (value);
}
}
oo.writeObject (null);
}
}
oo.writeObject (null);
}
/** Reads external.
*/
public void readExternal (ObjectInput oi) throws IOException, ClassNotFoundException {
for (;;) {
String file = (String)oi.readObject ();
if (file == null) break;
for (;;) {
String attr = (String)oi.readObject ();
if (attr == null) break;
Object o = oi.readObject ();
// backward compatibility
if (o instanceof java.rmi.MarshalledObject) {
o = ((java.rmi.MarshalledObject)o).get ();
o = new NbMarshalledObject (o);
}
// end of backward compatibility
if (o instanceof NbMarshalledObject) {
NbMarshalledObject value = (NbMarshalledObject)o;
setMarshalledAttr (file, attr, value);
}
}
}
}
}
}
/*
* Log
* 24 src-jtulach1.23 1/14/00 Jesse Glick Transient file
* attributes.
* 23 src-jtulach1.22 1/13/00 Ian Formanek NOI18N
* 22 src-jtulach1.21 1/12/00 Ian Formanek NOI18N
* 21 src-jtulach1.20 11/25/99 Jaroslav Tulach List.children () can
* return array that contains nulls
* 20 src-jtulach1.19 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun
* Microsystems Copyright in File Comment
* 19 src-jtulach1.18 8/6/99 Jaroslav Tulach Better synchronization.
* 18 src-jtulach1.17 7/25/99 Ian Formanek Exceptions printed to
* console only on "netbeans.debug.exceptions" flag
* 17 src-jtulach1.16 7/1/99 Jaroslav Tulach
* 16 src-jtulach1.15 6/25/99 Jaroslav Tulach Deadlock during
* compilation.
* 15 src-jtulach1.14 6/9/99 Jaroslav Tulach Executables can be in
* menu & toolbars.
* 14 src-jtulach1.13 6/8/99 Ian Formanek ---- Package Change To
* org.openide ----
* 13 src-jtulach1.12 6/8/99 Jaroslav Tulach extractJar
* 12 src-jtulach1.11 6/4/99 Jaroslav Tulach Now it is backward
* compatible.
* 11 src-jtulach1.10 6/4/99 Jaroslav Tulach Extended attributes do
* not need use of RMI
* 10 src-jtulach1.9 6/2/99 Jaroslav Tulach Safe version of
* serialization of attributes. Not backward compatible.
* 9 src-jtulach1.8 5/6/99 Jaroslav Tulach Survives when root of FS
* is deleted.
* 8 src-jtulach1.7 4/28/99 Petr Hamernik read-only attributes
* file is now readed
* 7 src-jtulach1.6 4/27/99 Michal Fadljevic
* 6 src-jtulach1.5 4/23/99 Jaroslav Tulach
* 5 src-jtulach1.4 4/13/99 Ales Novak NbObjectInputStream used
* - modules work now
* 4 src-jtulach1.3 3/26/99 Jesse Glick [JavaDoc]
* 3 src-jtulach1.2 3/26/99 Jaroslav Tulach Refresh & Bundles
* 2 src-jtulach1.1 3/26/99 Jaroslav Tulach
* 1 src-jtulach1.0 3/24/99 Jaroslav Tulach
* $
* Beta Change History:
* 0 Tuborg 0.11 --/--/98 Jaroslav Tulach made package private
*/